/**
 * @file fs.c
 * @author lcz (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-11-02
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "fs/fs.h"
#include "comm/types.h"
#include "comm/cpu_instr.h"
#include "comm/boot_info.h"
#include "tools/kilb.h"
#include "tools/log.h"
#include "dev/console.h"
#include "dev/dev.h"
#include "core/task.h"
#include <sys/file.h>
#include "dev/disk.h"
#include "os.cfg.h"
#include "fs/fatfs/fatfs.h"
// copy loader_32 read_disk()
// 从固定位置开始读写
// 用8M的地方放
//#define TEMP_ADDR (8*1024*1024)
// #define TEMP_FILE_ID 100
// static uint8_t TEMP_ADDR[100 * 1024];
#define FS_TABLE_SIZE 10
// 设备文件系统的回调
extern fs_op_t devfs_op;
extern fs_op_t fatfs_op;
// 挂载文件列表
static list_t mounted_list;

//  文件系统列表，文件系统包含该系统中支持的回调函数,文件的操作
static fs_t fs_tablep[FS_TABLE_SIZE];
static list_t free_list;

// temp_pos  相当于指向缓冲器的读写指针
static uint8_t *temp_pos;


// 根文件系统
static fs_t *root_fs;

/**
 * @brief 开启文件保护，互斥锁
 * @param fs 文件系统
 */
static void fs_protect(fs_t *fs)
{
    if (fs->mutex)
    {
        mutex_lock(fs->mutex);
    }
}
/**
 * @brief 解除保护，解锁
 * @param fs 文件系统
 */
static void fs_unprotect(fs_t *fs)
{
    if (fs->mutex)
    {
        mutex_unlock(fs->mutex);
    }
}

// 系统调用
int sys_opendir(const char *name, DIR *dir)
{
    fs_protect(root_fs);
    int err = root_fs->op->opendir(root_fs, name, dir);
    fs_unprotect(root_fs);
}
int sys_readdir(DIR *dir, struct dirent *dirent)
{
    fs_protect(root_fs);
    int err = root_fs->op->readdir(root_fs, dir, dirent);
    fs_unprotect(root_fs);
    return err;
}
int sys_closedir(DIR *dir)
{
    fs_protect(root_fs);
    int err = root_fs->op->closedir(root_fs, dir);
    fs_unprotect(root_fs);
}
/**
 * @brief 读磁盘文件
 */
static void read_disk(uint32_t sector, uint32_t sector_count, uint8_t *buf)
{
    // setting paramters and read disk
    outb(0x1f6, 0xe0);
    outb(0x1f2, (uint8_t)(sector_count >> 8));
    outb(0x1f3, (uint8_t)(sector >> 24));
    outb(0x1f4, 0x0);
    outb(0x1f5, 0x0);

    outb(0x1f2, (uint8_t)(sector_count));
    outb(0x1f3, (uint8_t)(sector));
    outb(0x1f4, (uint8_t)(sector >> 8));
    outb(0x1f5, (uint8_t)(sector >> 16));
    // send the command to read disk
    outb(0x1f7, 0x24);

    uint16_t *data_buf = (uint16_t *)buf;
    while (sector_count--)
    {
        // if device busy or data not ready, wait
        while ((inb(0x1f7) & 0x88) != 0x8)
        {
            // wait for
            // here can't use BISO interrupt, so don't print message
        }

        for (int i = 0; i < SECTOR_SIZE / 2; i++)
        {
            *data_buf++ = inw(0x1f0);
        }
    }
}

/**
 * @brief 判断路径是否有效
 */
static int is_path_valid(const char *path)
{
    if ((path == (const char *)0) || path[0] == '\0')
    {
        return 0;
    }
    return 1;
}

/**
 * @brief 路径转换为相应的数值，不对非数值进行检查
 * @param path 路径
 * @param num 存放路径解析出来的数值
 * @return * int 函数正确执行放回0
 */
int path_to_num(const char *path, int *num)
{
    int n = 0;
    const char *c = path;
    while (*c)
    {
        n = n * 10 + *c - '0';
        c++;
        // 得到10进制数
    }
    *num = n;
    return 0;
}
/**
 * @brief 获取路径的子路径，根据'/'进行拆解
 * @param path 路径
 * @return const char* 返回下一层路径
 */
const char *path_next_child(const char *path)
{
    const char *c = path;
    while (*c && (*c++ == '/'))
    {
    }
    while (&c && (*c++ != '/'))
    {
    }

    return *c ? c : (const char *)0;
}
/**
 * @brief 判断路径path是否已str开头，正确放回1，否者0
 */
int path_begin_with(const char *path, const char *str)
{
    const char *s1 = path, *s2 = str;
    while (*s1 && *s2 && (*s1 == *s2))
    {
        s1++;
        s2++;
    }
    // s2 先结束或者同时结束，那门path确实包含了str，以str开头
    return *s2 == '\0';
}
/**
 * @brief 判断路径是否合法（正确范围内）
 * @param file 文件id
 * @return int 正确返回0
 */
static int is_fd_bad(int file)
{
    if ((file < 0) && (file >= TASK_OFILE_NR))
    {
        return 1;
    }
    return 0;
}

/**
 * @brief 打开设备，
 * @param name 文件名
 * @param flags flags标志位
 * @param ... 不定参数
 * @return * int 放回一个整数唯一标识一个文件
 */
int sys_open(const char *name, int flags, ...)
{
    // 临时使用，保留shell加载的功能
    // if (kernel_strncmp(name, "/shell.elf", 4) == 0)
    // {
    //     // 暂时直接从扇区1000上读取, 读取大概40KB，足够了
    //     // read_disk(5000, 80, (uint8_t *)TEMP_ADDR);
    //     int dev_id = dev_open(DEV_DISK, 0xa0, (void *)0);
    //     // 读取的大小与开始地址以扇区为单位
    //     dev_read(dev_id, 5000, (uint8_t *)TEMP_ADDR, 80);
    //     temp_pos = (uint8_t *)TEMP_ADDR;
    //     return TEMP_FILE_ID;
    // }

    // 分配文件描述符链接
    file_t *file = file_alloc();
    if (!file)
    {
        return -1;
    }

    int fd = task_alloc_fd(file);
    if (fd < 0)
    {
        goto sys_open_failed;
    }

    // 检查名称是否以挂载点开头，如果没有，则认为name在根目录下
    // 即只允许根目录下的遍历
    fs_t *fs = (fs_t *)0;
    list_node_t *node = list_first(&mounted_list);
    while (node)
    {
        // 遍历已经挂载的文件系统，存在就设置名称
        fs_t *curr = list_node_parent(node, fs_t, node);
        if (path_begin_with(name, curr->mount_point))
        {
            fs = curr;
            break;
        }
        node = list_node_next(node);
    }
    // 若路径为空则默认为根文件系统
    if (fs)
    {
        name = path_next_child(name);
    }
    else
    {
        fs = root_fs;
    }
    // 文件模式，只读只写等
    file->mode = flags;
    // 设置文件的文件系统
    file->fs = fs;
    kernel_strncpy(file->file_name, name, FILE_NAME_SIZE);

    // 开启保护
    fs_protect(fs);
    // 调用相应文件系统的的open回调函数
    int err = fs->op->open(fs, name, file);
    if (err < 0)
    {
        fs_unprotect(fs);

        log_printf("open %s failed.", name);
        return -1;
    }
    fs_unprotect(fs);

    return fd;
sys_open_failed:
    file_free(file);
    if (fd >= 0)
    {
        task_remove_fd(fd);
    }
    return -1;
}

/**
 * @brief 将temp_pos处读取len个字节到ptr处
 *
 * @param file 文件标识
 * @param ptr 目标地址
 * @param len 长度
 * @return * int 读取的长度
 */
int sys_read(int file, char *ptr, int len)
{

    // if (file == TEMP_FILE_ID)
    // {
    //     kernel_memcpy(ptr, temp_pos, len);
    //     temp_pos += len;
    //     return len;
    // }

    if (is_fd_bad(file) || !ptr || !len)
    {
        return 0;
    }
    //获取file结构
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("file not opened");
        return -1;
    }
    // open的读取操作最终转换成flags写进mode中
    if (p_file->mode == O_WRONLY)
    {
        log_printf("file is write only");
        return -1;
    }
    // 读取文件
    fs_t *fs = p_file->fs;
    // fs_protect(fs);
    int err = fs->op->read(ptr, len, p_file);
    // fs_unprotect(fs);
    return err;
}
int sys_write(int file, char *ptr, int len)
{
    if (is_fd_bad(file) || !ptr || !len)
    {
        return 0;
    }

    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("file not opened");
        return -1;
    }

    if (p_file->mode == O_RDONLY)
    {
        log_printf("file is read only");
        return -1;
    }

    // 写入文件
    fs_t *fs = p_file->fs;
    fs_protect(fs);
    int err = fs->op->write(ptr, len, p_file);
    fs_unprotect(fs);
    return err;
}
/**
 * @brief 调整到缓冲区的指定偏移处
 *
 * @param file
 * @param ptr
 * @param dir
 * @return * int
 */
int sys_lseek(int file, int ptr, int dir)
{
    // if (file == TEMP_FILE_ID)
    // {
    //     temp_pos = (uint8_t *)(TEMP_ADDR + ptr);
    //     return 0;
    // }

    if (is_fd_bad(file))
    {
        return -1;
    }

    //获取file结构
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("file not opened");
        return -1;
    }

    // 写入文件
    fs_t *fs = p_file->fs;

    fs_protect(fs);
    int err = fs->op->seek(p_file, ptr, dir);
    fs_unprotect(fs);
    return err;
}
int sys_close(int file)
{
    // 临时文件 shell文件
    // if (file == TEMP_FILE_ID)
    // {
    //     return 0;
    // }
    // 判断合法性
    if (is_fd_bad(file))
    {
        log_printf("file error");
        return -1;
    }
    //获取file结构
    file_t *p_file = task_file(file);
    if (p_file == (file_t *)0)
    {
        log_printf("file not opened. %d", file);
        return -1;
    }
    // 引用数必须大于0 否者错误
    ASSERT(p_file->ref > 0);
    // 引用数为1 减1后释放
    if (p_file->ref-- == 1)
    {
        // 胡哦去文件系统的关闭函数，这就是抽象，我们调统一的接口函数
        // 及op->close函数 后面根据不同的文件系统，不同的设备执行具体的函数（驱动程序）
        // 而应用层更加过分，直接调用系统调用即可
        fs_t *fs = p_file->fs;

        fs_protect(fs);
        fs->op->close(p_file);
        fs_unprotect(fs);
        file_free(p_file);
    }
    // 进程删除文件描述符
    task_remove_fd(file);
    return 0;
}
/**
 * 判断文件描述符与tty关联
 */
int sys_isatty(int file)
{
    if (is_fd_bad(file))
    {
        return 0;
    }
    //获取file结构
    file_t *p_file = task_file(file);
    if (p_file == (file_t *)0)
    {
        log_printf("file not opened. %d", file);
        return -1;
    }
    return p_file->type == FILE_TTY;
}

/**
 * @brief 获取文件状态
 */
int sys_fstat(int file, struct stat *st)
{
    if (is_fd_bad(file))
    {
        return 0;
    }
    //获取file结构
    file_t *p_file = task_file(file);
    if (p_file == (file_t *)0)
    {
        return -1;
    }
    // 获取文件系统
    fs_t *fs = p_file->fs;
    // 使用前将stat进行清零
    kernel_memset(st, 0, sizeof(struct stat));
    // 保护并调用文件系统的响应函数
    fs_protect(fs);
    int err = fs->op->stat(p_file, st);
    fs_unprotect(fs);
    return err;
}
/**
 * 复制一个文件描述符
 */
int sys_dup(int file)
{
    // file合法性校验
    // 超出进程所能打开的全部，退出
    if (is_fd_bad(file))
    {
        log_printf("file(%d) is not valid.", file);
        return -1;
    }
    // 根据当前进程的文件描述符file找到对应的文件p_file
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("file not opened");
        return -1;
    }
    // fd指向同一个结构，找一个空文件描述符指向这个文件
    int fd = task_alloc_fd(p_file);
    if (fd >= 0)
    {
        // p_file->ref++;
        file_inc_ref(p_file);
        return fd;
    }
    log_printf("no task file invalid!");
}
/**
 * @brief 更具文件系统类型获取相应的回调函数
 * @param type 文件系统类型
 * @param major 主设备号
 * @return  fs_op_t*
 */
static fs_op_t *get_fs_op(fs_type_t type, int major)
{
    switch (type)
    {
    case FS_FAT16:
        return &(fatfs_op);

    case FS_DEVFS:
        return &(devfs_op);
    default:
        return (fs_op_t *)0;
    }
}
/**
 * @brief io控制接口，目前仅实现了回写的取消
 * @param fd 文件描述符
 * @param cmd 命令
 * @return * int 
 */
int sys_ioctl(int fd, int cmd, int arg0, int arg1){
    // file合法性校验
    // 超出进程所能打开的全部，退出
    if (is_fd_bad(fd))
    {
        log_printf("file(%d) is not valid.", fd);
        return -1;
    }
    // 根据当前进程的文件描述符file找到对应的文件p_file
    file_t *p_file = task_file(fd);
    if (!p_file)
    {
        log_printf("file not opened");
        return -1;
    }
    // 获取该文件描述符对应的文件结构获取对应文件系统结构，调用相应的接口函数
    fs_t *fs = p_file->fs;
    // 文件保护
    fs_protect(fs);
    int err = fs->op->ioctl(p_file, cmd, arg0, arg1);
    fs_unprotect(fs);
    return err;
}
/**
 * @brief 删除磁盘上的文件目录，我们将目录表项设置为失效后将删除簇链，并清空簇链中的内容
 * @param path 文件路径
 * @return * int 
 */
int sys_unlink(const char *path)
{
    fs_protect(root_fs);
    int err = root_fs->op->unlink(root_fs,path);
    fs_unprotect(root_fs);
}


    /**
     * @brief 挂载文件列表初始化
     */
    static void mount_list_init(void)
    {
        list_init(&free_list);
        for (int i = 0; i < FS_TABLE_SIZE; i++)
        {
            // 文件系统无论有无，先加入链表先
            // 一个结点指向一个文件系统，我们挂载文件系统的时候就从此处分配结点（文件系统给对应的文件）
            list_insert_first(&free_list, &fs_tablep[i].node);
        }
        list_init(&mounted_list);
    }

    /**
     * @brief 文件系统中顶层抽象的挂载操作，具体细节的挂载操作由对应文件系统回调函数完成
     * 挂载及将mounted列表中添加相应的回调函数，将对应的文件系统fs添加到挂载队列中即可
     * @param type 文件系统类型
     * @param mount_point 挂载点
     * @param dev_major 主设备号
     * @param dev_minor 次设备号
     * @return * fs_t* 对应的文件系统结构体
     */
    static fs_t *mount(fs_type_t type, char *mount_point, int dev_major, int dev_minor)
    {

        fs_t *fs = (fs_t *)0;
        // 打印信息
        log_printf("mount file system, name is %s,dev: %x", mount_point, dev_major);
        // 获取结点
        list_node_t *curr = list_first(&mounted_list);
        // 文件系统已经挂载 我们遍历一下看看是不是已经挂载了
        while (curr)
        {
            fs_t *fs = list_node_parent(curr, fs_t, node);
            if (kernel_strncmp(fs->mount_point, mount_point, FS_MOUNTP_SIZE) == 0)
            {
                log_printf("fs already mounted");
                goto mount_failed;
            }

            curr = list_node_next(curr);
        }

        // 挂载失败了哦！ 我们挂载的时候是从空闲的挂载列表获取结点来挂载的哦
        list_node_t *free_node = list_remove_first(&free_list);
        if (!free_node)
        {
            log_printf("no free fs, mount failed");
            goto mount_failed;
        }

        // free_node 分配成功 获取文件系统结构指针
        fs = list_node_parent(free_node, fs_t, node);

        // 我们要设备op 操作 回调函数表 要根据文件系统类型来获取回调函数表
        fs_op_t *op = get_fs_op(type, dev_major);
        if (!op)
        {
            log_printf("unsupported fs type: %d", type);
            goto mount_failed;
        }
        // 清空文件结构并初始化
        kernel_memset(fs, 0, sizeof(fs_t));
        kernel_strncpy(fs->mount_point, mount_point, FS_MOUNTP_SIZE);
        fs->op = op;

        // 调用fs->op中的挂载操作，进行具体的挂载
        if (op->mount(fs, dev_major, dev_minor) < 0)
        {
            log_printf("mount fs %s failed.", mount_point);
            goto mount_failed;
        }
        // 加入挂载链表
        list_insert_last(&mounted_list, &fs->node);

        return fs;

    mount_failed:
        // 挂载失败
        // 将获取的结点放回去
        if (fs)
        {
            list_insert_last(&free_list, &fs->node);
        }
        return (fs_t *)0;
    }
    /**
     * @brief 文件系统初始化
     */
    void fs_init(void)
    {
        mount_list_init();
        file_table_init();
        disk_init();
        fs_t *fs = mount(FS_DEVFS, "/dev/", 0, 0);
        ASSERT(fs != (fs_t *)0);

        // 给shell指定初始的目录 根文件系统 主要的操作对象
        root_fs = mount(FS_FAT16, "/home", ROOT_DEV);
        ASSERT(root_fs != (fs_t *)0);
    }
